/*
 * ProGuard -- shrinking, optimization, obfuscation, and preverification of Java bytecode.
 *
 * Copyright (c) 2002-2018 GuardSquare NV
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package proguard.classfile.editor;

import proguard.classfile.*;

/**
 * This class can add interfaces and class members to a given class. Elements to be added must be filled out beforehand,
 * including their references to the constant pool.
 *
 * @author Eric Lafortune
 */
public class ClassEditor {
    private static final boolean DEBUG = false;

    private ProgramClass targetClass;

    /**
     * Creates a new ClassEditor that will edit elements in the given target class.
     */
    public ClassEditor(ProgramClass targetClass) {
        this.targetClass = targetClass;
    }

    /**
     * Adds the given interface.
     */
    public void addInterface(int interfaceConstantIndex) {
        int interfacesCount = targetClass.u2interfacesCount;
        int[] interfaces = targetClass.u2interfaces;

        // Make sure there is enough space for the new interface.
        if (interfaces.length <= interfacesCount) {
            targetClass.u2interfaces = new int[interfacesCount + 1];
            System.arraycopy(interfaces, 0, targetClass.u2interfaces, 0, interfacesCount);
            interfaces = targetClass.u2interfaces;
        }

        if (DEBUG) {
            System.out.println(targetClass.getName() + ": adding interface ["
                + targetClass.getClassName(interfaceConstantIndex) + "]");
        }

        // Add the interface.
        interfaces[targetClass.u2interfacesCount++] = interfaceConstantIndex;
    }

    /**
     * Removes the given interface.
     */
    public void removeInterface(int interfaceConstantIndex) {
        int interfacesCount = targetClass.u2interfacesCount;
        int[] interfaces = targetClass.u2interfaces;

        int interfaceIndex = findInterfaceIndex(interfaceConstantIndex);

        // Shift the interface entries.
        System.arraycopy(interfaces, interfaceIndex + 1, interfaces, interfaceIndex,
            interfacesCount - interfaceIndex - 1);

        // Clear the last entry.
        interfaces[--targetClass.u2interfacesCount] = 0;
    }

    /**
     * Finds the index of the given interface in the target class.
     */

    private int findInterfaceIndex(int interfaceConstantIndex) {
        int interfacesCount = targetClass.u2interfacesCount;
        int[] interfaces = targetClass.u2interfaces;

        for (int index = 0; index < interfacesCount; index++) {
            if (interfaces[index] == interfaceConstantIndex) {
                return index;
            }
        }

        return interfacesCount;
    }

    /**
     * Adds the given field.
     */
    public void addField(Field field) {
        int fieldsCount = targetClass.u2fieldsCount;
        Field[] fields = targetClass.fields;

        // Make sure there is enough space for the new field.
        if (fields.length <= fieldsCount) {
            targetClass.fields = new ProgramField[fieldsCount + 1];
            System.arraycopy(fields, 0, targetClass.fields, 0, fieldsCount);
            fields = targetClass.fields;
        }

        if (DEBUG) {
            System.out.println(targetClass.getName() + ": adding field [" + field.getName(targetClass) + " "
                + field.getDescriptor(targetClass) + "]");
        }

        // Add the field.
        fields[targetClass.u2fieldsCount++] = field;
    }

    /**
     * Removes the given field. Note that removing a field that is still being referenced can cause unpredictable
     * effects.
     */
    public void removeField(Field field) {
        int fieldsCount = targetClass.u2fieldsCount;
        Field[] fields = targetClass.fields;

        int fieldIndex = findFieldIndex(field);

        // Shift the field entries.
        System.arraycopy(fields, fieldIndex + 1, fields, fieldIndex, fieldsCount - fieldIndex - 1);

        // Clear the last entry.
        fields[--targetClass.u2fieldsCount] = null;
    }

    /**
     * Finds the index of the given field in the target class.
     */

    private int findFieldIndex(Field field) {
        int fieldsCount = targetClass.u2fieldsCount;
        Field[] fields = targetClass.fields;

        for (int index = 0; index < fieldsCount; index++) {
            if (fields[index].equals(field)) {
                return index;
            }
        }

        return fieldsCount;
    }

    /**
     * Adds the given method.
     */
    public void addMethod(Method method) {
        int methodsCount = targetClass.u2methodsCount;
        Method[] methods = targetClass.methods;

        // Make sure there is enough space for the new method.
        if (methods.length <= methodsCount) {
            targetClass.methods = new ProgramMethod[methodsCount + 1];
            System.arraycopy(methods, 0, targetClass.methods, 0, methodsCount);
            methods = targetClass.methods;
        }

        if (DEBUG) {
            System.out.println(targetClass.getName() + ": adding method [" + method.getName(targetClass)
                + method.getDescriptor(targetClass) + "]");
        }

        // Add the method.
        methods[targetClass.u2methodsCount++] = method;
    }

    /**
     * Removes the given method. Note that removing a method that is still being referenced can cause unpredictable
     * effects.
     */
    public void removeMethod(Method method) {
        int methodsCount = targetClass.u2methodsCount;
        Method[] methods = targetClass.methods;

        int methodIndex = findMethodIndex(method);

        // Shift the method entries.
        System.arraycopy(methods, methodIndex + 1, methods, methodIndex, methodsCount - methodIndex - 1);

        // Clear the last entry.
        methods[--targetClass.u2methodsCount] = null;
    }

    /**
     * Finds the index of the given method in the target class.
     */

    private int findMethodIndex(Method method) {
        int methodsCount = targetClass.u2methodsCount;
        Method[] methods = targetClass.methods;

        for (int index = 0; index < methodsCount; index++) {
            if (methods[index].equals(method)) {
                return index;
            }
        }

        return methodsCount;
    }
}
